using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.IO;//for saving the reading the calibration data
using OSC.NET;
using WiimoteLib;

namespace WiimoteTUIO
{
	public partial class Form1 : Form
	{
        //instance of the wii remote
		Wiimote wm = new Wiimote();

        bool enableTUIO = false;
        string TUIOIp;
        int TUIOPort;
        int TUIOSessionsId = 0;
        int[] SessionIR = new int[4] { 1, 2, 3, 4 };
        int TUIOfseq = 0;

        int screenWidth = 1024;//defaults, gets replaced by actual screen size
        int screenHeight = 768;

        int calibrationState = 0;
        float calibrationMargin = .1f;

        CalibrationForm cf = null;

        Warper warper = new Warper();
        float[] srcX = new float[4];
        float[] srcY = new float[4];
        float[] dstX = new float[4];
        float[] dstY = new float[4];

        WiimoteState lastWiiState = new WiimoteState(); //helps with event firing

        //end keyboard and mouse input emulation variables----------------------------------------

        Mutex mut = new Mutex();

		public Form1()
		{
            screenWidth = Screen.GetBounds(this).Width;
            screenHeight = Screen.GetBounds(this).Height;
            //defineINPUTS();
            InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
            //add event listeners to changes in the wiiremote
            //fired for every input report - usually 100 times per second if acclerometer is enabled
			wm.WiimoteChanged += new WiimoteChangedEventHandler(wm_OnWiimoteChanged); 
            //fired when the extension is attached on unplugged
            wm.WiimoteExtensionChanged += new WiimoteExtensionChangedEventHandler(wm_OnWiimoteExtensionChanged);

            TUIOIp = txtTuioIp.Text;
            TUIOPort = Int32.Parse(txtTuioPort.Text);

            try
            {
                //connect to wii remote
                wm.Connect();

                //set what features you want to enable for the remote, look at Wiimote.InputReport for options
                wm.SetReportType(Wiimote.InputReport.IRAccel, true);
                
                //get status
                wm.GetStatus();
                
                //set wiiremote LEDs with this enumerated ID
                int count = wm.GetNumConnectRemotes();
                switch (count)
                {
                    case 1:
                        wm.SetLEDs(true, false, false, false);
                        break;
                    case 2:
                        wm.SetLEDs(false, true, false, false);
                        break;
                    case 3:
                        wm.SetLEDs(false, false, true, false);
                        break;
                    case 4:
                        wm.SetLEDs(false, false, false, true);
                        break;
                    case 5:
                        wm.SetLEDs(true, false, false, true);
                        break;
                    case 6:
                        wm.SetLEDs(false, true, false, true);
                        break;
                    case 7:
                        wm.SetLEDs(false, false, true, true);
                        break;
                    case 8:
                        wm.SetLEDs(true, false, true, true);
                        break;
                    case 9:
                        wm.SetLEDs(false, true, true, true);
                        break;
                    case 10:
                        wm.SetLEDs(true, true, true, true);
                        break;
                    default:
                        wm.SetLEDs(false, false, false, false);
                        break;
                }

                udpWriter = new OSCTransmitter(TUIOIp, TUIOPort);

            }
            catch (Exception x)
            {
                MessageBox.Show("Exception: " + x.Message);
                this.Close();
            }
            loadCalibrationData();
		}

		void wm_OnWiimoteExtensionChanged(object sender, WiimoteExtensionChangedEventArgs args)
		{

            //if extension attached, enable it
			if(args.Inserted)
				wm.SetReportType(Wiimote.InputReport.IRExtensionAccel, true);
			else
				wm.SetReportType(Wiimote.InputReport.IRAccel, true);
		}

		void wm_OnWiimoteChanged(object sender, WiimoteChangedEventArgs args)
		{
            mut.WaitOne();

            try
            {
                //extract the wiimote state
                WiimoteState ws = args.WiimoteState;

                if (ws.IRState.Found1)
                {

                    if (!lastWiiState.IRState.Found1)
                    {

                        int x = ws.IRState.RawX1;
                        int y = ws.IRState.RawY1;

                        switch (calibrationState)
                        {
                            case 1:
                                srcX[calibrationState - 1] = x;
                                srcY[calibrationState - 1] = y;
                                calibrationState = 2;
                                doCalibration();
                                break;
                            case 2:
                                srcX[calibrationState - 1] = x;
                                srcY[calibrationState - 1] = y;
                                calibrationState = 3;
                                doCalibration();
                                break;
                            case 3:
                                srcX[calibrationState - 1] = x;
                                srcY[calibrationState - 1] = y;
                                calibrationState = 4;
                                doCalibration();
                                break;
                            case 4:
                                srcX[calibrationState - 1] = x;
                                srcY[calibrationState - 1] = y;
                                calibrationState = 5;
                                doCalibration();
                                break;
                            default:
                                break;
                        }
                    }
                }


                if (enableTUIO)
                {
                    // put in my code lousy :P
                    Object[] ir1 = new Object[7];
                    Object[] ir2 = new Object[7];
                    Object[] ir3 = new Object[7];
                    Object[] ir4 = new Object[7];

                    // Create an new TUIO Bundle
                    OSCBundle OscB = new OSCBundle();
                    OscB.Append(TUIO.TUIOFseq(TUIOfseq));

                    ArrayList sessions = new ArrayList();

                    if (ws.IRState.Found1)
                    {
                        if (lastWiiState.IRState.RawX1 != ws.IRState.RawX1 && lastWiiState.IRState.RawY1 != ws.IRState.RawY1)
                        {
                            float warpedX = ws.IRState.RawX1;
                            float warpedY = ws.IRState.RawY1;
                            warper.warp(warpedX, warpedY, ref warpedX, ref warpedY);

                            if (!lastWiiState.IRState.Found1)
                                SessionIR[0] = ++TUIOSessionsId;

                            OscB.Append(TUIO.TUIO2DcurExt(SessionIR[0], (warpedX / screenWidth), (warpedY / screenHeight), 0, 0, 0,(float)ws.IRState.Size1, (float)ws.IRState.Size1));
                        }
                        sessions.Add(SessionIR[0]);
                    }

                    if (ws.IRState.Found2)
                    {
                        if (lastWiiState.IRState.RawX2 != ws.IRState.RawX2 && lastWiiState.IRState.RawY2 != ws.IRState.RawY2)
                        {
                            float warpedX = ws.IRState.RawX2;
                            float warpedY = ws.IRState.RawY2;
                            warper.warp(warpedX, warpedY, ref warpedX, ref warpedY);

                            if (!lastWiiState.IRState.Found2)
                                SessionIR[1] = ++TUIOSessionsId;

                            OscB.Append(TUIO.TUIO2DcurExt(SessionIR[1], (warpedX / screenWidth), (warpedY / screenHeight), 0, 0, 0, (float)ws.IRState.Size2, (float)ws.IRState.Size2));
                        }
                        sessions.Add(SessionIR[1]);
                    }

                    if (ws.IRState.Found3)
                    {
                        if (lastWiiState.IRState.RawX3 != ws.IRState.RawX3 && lastWiiState.IRState.RawY3 != ws.IRState.RawY3)
                        {
                            float warpedX = ws.IRState.RawX3;
                            float warpedY = ws.IRState.RawY3;
                            warper.warp(warpedX, warpedY, ref warpedX, ref warpedY);

                            if (!lastWiiState.IRState.Found3)
                                SessionIR[2] = ++TUIOSessionsId;

                            OscB.Append(TUIO.TUIO2DcurExt(SessionIR[2], (warpedX / screenWidth), (warpedY / screenHeight), 0, 0, 0, (float)ws.IRState.Size3, (float)ws.IRState.Size3));
                        }
                        sessions.Add(SessionIR[2]);
                    }

                    if (ws.IRState.Found4)
                    {
                        if (lastWiiState.IRState.RawX4 != ws.IRState.RawX4 && lastWiiState.IRState.RawY4 != ws.IRState.RawY4)
                        {
                            float warpedX = ws.IRState.RawX4;
                            float warpedY = ws.IRState.RawY4;
                            warper.warp(warpedX, warpedY, ref warpedX, ref warpedY);

                            if (!lastWiiState.IRState.Found4)
                                SessionIR[3] = ++TUIOSessionsId;

                            OscB.Append(TUIO.TUIO2DcurExt(SessionIR[3], (warpedX / screenWidth), (warpedY / screenHeight), 0, 0, 0, (float)ws.IRState.Size4, (float)ws.IRState.Size4));
                        }
                        sessions.Add(SessionIR[3]);

                    }

                    OscB.Append(TUIO.TUIOAlive(sessions));
                    udpWriter.Send(OscB);
                    TUIOfseq++;
                }


                lastWiiState.IRState.Found1 = ws.IRState.Found1;
                lastWiiState.IRState.RawX1 = ws.IRState.RawX1;
                lastWiiState.IRState.RawY1 = ws.IRState.RawY1;
                lastWiiState.IRState.Found2 = ws.IRState.Found2;
                lastWiiState.IRState.RawX2 = ws.IRState.RawX2;
                lastWiiState.IRState.RawY2 = ws.IRState.RawY2;
                lastWiiState.IRState.Found3 = ws.IRState.Found3;
                lastWiiState.IRState.RawX3 = ws.IRState.RawX3;
                lastWiiState.IRState.RawY3 = ws.IRState.RawY3;
                lastWiiState.IRState.Found4 = ws.IRState.Found4;
                lastWiiState.IRState.RawX4 = ws.IRState.RawX4;
                lastWiiState.IRState.RawY4 = ws.IRState.RawY4;


                //draw battery value on GUI
                BeginInvoke((MethodInvoker)delegate() { pbBattery.Value = (ws.Battery > 0xc8 ? 0xc8 : (int)ws.Battery); });
                float f = (((100.0f * 48.0f * (float)(ws.Battery / 48.0f))) / 192.0f);
                BeginInvoke((MethodInvoker)delegate() { lblBattery.Text = f.ToString("F"); });

                //check the GUI check boxes if the IR dots are visible
                String irstatus = "Visible IR dots: ";
                if (ws.IRState.Found1)
                    irstatus += "1 ";
                if (ws.IRState.Found2)
                    irstatus += "2 ";
                if (ws.IRState.Found3)
                    irstatus += "3 ";
                if (ws.IRState.Found4)
                    irstatus += "4 ";

                BeginInvoke((MethodInvoker)delegate() { lblIRvisible.Text = irstatus; });
            }
            finally
            {
                mut.ReleaseMutex();
            }
        }


        public void loadCalibrationData()
        {
            // create reader & open file
            try
            {
                TextReader tr = new StreamReader("calibration.dat");
                for (int i = 0; i < 4; i++)
                {
                    srcX[i] = float.Parse(tr.ReadLine());
                    srcY[i] = float.Parse(tr.ReadLine());
                }

                // close the stream
                tr.Close();
            }
            catch (System.IO.FileNotFoundException)
            {
                //no prexsting calibration
                return;
            }

            warper.setDestination(  screenWidth * calibrationMargin,
                                    screenHeight * calibrationMargin,
                                    screenWidth * (1.0f-calibrationMargin),
                                    screenHeight * calibrationMargin,
                                    screenWidth * calibrationMargin,
                                    screenHeight * (1.0f - calibrationMargin),
                                    screenWidth * (1.0f - calibrationMargin),
                                    screenHeight * (1.0f - calibrationMargin));
            warper.setSource(srcX[0], srcY[0], srcX[1], srcY[1], srcX[2], srcY[2], srcX[3], srcY[3]);

            warper.computeWarp();
            enableTUIO = true;
            BeginInvoke((MethodInvoker)delegate() { cbCursorControl.Checked = enableTUIO; });

        }

        public void saveCalibrationData()
        {
            TextWriter tw = new StreamWriter("calibration.dat");

            // write a line of text to the file
            for (int i = 0; i < 4; i++)
            {
                tw.WriteLine(srcX[i]);
                tw.WriteLine(srcY[i]);
            }
            // close the stream
            tw.Close();
        }

        public void doCalibration(){
            if (cf == null)
                return;
            int x = 0;
            int y = 0;
            int size = 25;
            Pen p = new Pen(Color.Red);
            switch (calibrationState)
            {
                case 1:
                    x = (int)(screenWidth * calibrationMargin);
                    y = (int)(screenHeight * calibrationMargin);
                    cf.showCalibration(x, y, size, p);
                    dstX[calibrationState - 1] = x;
                    dstY[calibrationState - 1] = y;
                    break;
                case 2:
                    x = screenWidth - (int)(screenWidth * calibrationMargin);
                    y = (int)(screenHeight * calibrationMargin);
                    cf.showCalibration(x, y, size, p);
                    dstX[calibrationState - 1] = x;
                    dstY[calibrationState - 1] = y;
                    break;
                case 3:
                    x = (int)(screenWidth * calibrationMargin);
                    y = screenHeight -(int)(screenHeight * calibrationMargin);
                    cf.showCalibration(x, y, size, p);
                    dstX[calibrationState - 1] = x;
                    dstY[calibrationState - 1] = y;
                    break;
                case 4:
                    x = screenWidth - (int)(screenWidth * calibrationMargin);
                    y = screenHeight -(int)(screenHeight * calibrationMargin);
                    cf.showCalibration(x, y, size, p);
                    dstX[calibrationState - 1] = x;
                    dstY[calibrationState - 1] = y;
                    break;
                case 5:
                    //compute warp
                    warper.setDestination(dstX[0], dstY[0], dstX[1], dstY[1], dstX[2], dstY[2], dstX[3], dstY[3]);
                    warper.setSource(srcX[0], srcY[0], srcX[1], srcY[1], srcX[2], srcY[2], srcX[3], srcY[3]);
                    warper.computeWarp();
                    cf.doneCalibration();
                    cf = null;
                    calibrationState = 0;
                    enableTUIO = true;
                    BeginInvoke((MethodInvoker)delegate() { cbCursorControl.Checked = enableTUIO; });
                    saveCalibrationData();
                    break;
                default:
                    break;
            }

        }

		private void Form1_FormClosed(object sender, FormClosedEventArgs e)
		{
            //disconnect the wiimote
			wm.Disconnect();
		}

        private void btnCalibrate_Click(object sender, EventArgs e)
        {
            if (cf == null)
            {
                cf = new CalibrationForm();
                cf.Show();
            }
            if (cf.IsDisposed)
            {
                cf = new CalibrationForm();
                cf.Show();
            }
            calibrationState = 1;
            doCalibration();
        }

        private void restartUDP(string Ip, int Port)
        {
            TUIOIp = Ip;
            TUIOPort = Port;

            if (enableTUIO)
            {
                udpWriter = new OSCTransmitter(TUIOIp, TUIOPort);
            }
            else
            {
                udpWriter = null;
            }
        }

        private void cbCursorControl_CheckedChanged(object sender, EventArgs e)
        {
            enableTUIO = cbCursorControl.Checked;
        }

        private void txtTuioIp_TextChanged(object sender, EventArgs e)
        {
            TUIOIp = txtTuioIp.Text;
            TUIOPort = Int32.Parse(txtTuioPort.Text);
            restartUDP(TUIOIp, TUIOPort);

        }

        private void txtTuioPort_TextChanged(object sender, EventArgs e)
        {
            TUIOIp = txtTuioIp.Text;
            TUIOPort = Int32.Parse(txtTuioPort.Text);
            restartUDP(TUIOIp, TUIOPort);
        }
	}
}